;========= CONFIGURABLE TEMPERATURE PARAMETERS =========
var bedTargetTemp = 150      ; Default target bed temperature for PID tuning (°C)
var useChamber = false       ; Whether to pre-heat the chamber (set later based on user choice)

; ---- User confirmation and temperature selection ----
M291 S4 K{"Start with 150°C","Custom temperature","Cancel"} R"Bed Heater PID Tuning" P"Do not leave printer unattended."

if input == 0
  ; Use default temperature (150°C)
  echo "Starting bed PID tuning at default temperature: " ^ var.bedTargetTemp ^ "°C"
elif input == 1
  ; Ask for custom temperature
  M291 J1 L120 H200 F{var.bedTargetTemp} P"Enter target bed temperature (120-200°C)" R"Custom PID Temperature" S5
  set var.bedTargetTemp = input
  echo "Starting bed PID tuning at custom temperature: " ^ var.bedTargetTemp ^ "°C"
else
  abort "PID tuning cancelled by user."

; ---- Chamber heating prompt for high-temperature targets ----
if var.bedTargetTemp > 140
    var chamberMsg = "Target is " ^ var.bedTargetTemp ^ "°C."
    set var.chamberMsg = var.chamberMsg ^ " Chamber heating is needed for accurate PID tuning."
    set var.chamberMsg = var.chamberMsg ^ "<br><br>Please CLOSE the front door and lid before proceeding."
    set var.chamberMsg = var.chamberMsg ^ "<br><br>Chamber will heat to 100°C first (wait for 60°C)."
    set var.chamberMsg = var.chamberMsg ^ "<br><br>Or skip chamber heating and tune with ambient air only."
    M291 S4 K{"OK - Proceed","Skip chamber heating"} F0 R"Chamber Heating Required" P{var.chamberMsg}
    if input == 0
        set var.useChamber = true
        echo "User selected: proceed with chamber heating"
    else
        set var.useChamber = false
        echo "User selected: skip chamber heating"

; ---- HEPA Filter Option ----
var hepaOn = false
M291 S4 K{"Default (automatic)","On (force during test)"} R"HEPA Filter" P"Select HEPA filter mode during PID tuning.<br><br><b>Default</b> - thermostatic (auto based on chamber temp).<br><b>On</b> - force HEPA on for the duration of the test."
if input == 1
    set var.hepaOn = true
    echo "HEPA filter will be forced ON during PID tuning"
else
    echo "HEPA filter will remain in default thermostatic mode"

; ---- Pre-Test Heater Fault Check ----
var faultFound = false
var faultMsg = "Heater fault(s) detected before PID tuning:<br><br>"

if heat.heaters[2].state == "fault"
    set var.faultFound = true
    set var.faultMsg = var.faultMsg ^ "• <b>Bed Heater (H2)</b> is in FAULT state<br>"

if var.useChamber && heat.heaters[3].state == "fault"
    set var.faultFound = true
    set var.faultMsg = var.faultMsg ^ "• <b>Chamber Heater (H3)</b> is in FAULT state<br>"

if var.faultFound
    set var.faultMsg = var.faultMsg ^ "<br>Reset the fault and continue?"
    M98 P"0:/sys/led/fault.g"
    M291 R"Heater Fault Detected" P{var.faultMsg} S4 K{"Reset Faults & Continue","Cancel"}
    if input == 0
        M562                                                                                  ; Reset all heater faults
        G4 S2                                                                                 ; Wait for faults to clear
        ; Verify faults are cleared
        if heat.heaters[2].state == "fault" || (var.useChamber && heat.heaters[3].state == "fault")
            M98 P"0:/sys/led/fault.g"
            M291 R"Fault Reset Failed" P"Heater faults could not be cleared.<br>Check hardware and wiring." S2
            M98 P"0:/sys/led/resetstatus.g"
            abort "Heater faults could not be cleared"
        ; Wait 5s and re-check — faults can reappear shortly after reset
        G4 S5
        if heat.heaters[2].state == "fault" || (var.useChamber && heat.heaters[3].state == "fault")
            M98 P"0:/sys/led/fault.g"
            M291 R"Fault Reappeared" P"Heater fault reappeared after reset.<br>This indicates a hardware issue.<br>Check wiring and thermistor connections." S2
            M98 P"0:/sys/led/resetstatus.g"
            abort "Heater fault reappeared after reset"
        M98 P"0:/sys/led/resetstatus.g"
        echo "Heater faults cleared successfully"
    else
        M98 P"0:/sys/led/resetstatus.g"
        abort "PID tuning cancelled - heater faults not cleared"

; Disable Fans
M106 P3 S0
M106 P1 S0

;Cool Down Tools
M568 P0 S0 R0
M568 P1 S0 R0
M568 P2 S0 R0
M568 P3 S0 R0

; Start chamber heating early so it warms up during cooldown and homing
if var.useChamber
    M141 S100                                                                                 ; Start chamber early — it needs time to reach 60°C
    echo "Chamber heating started early"

; ---- Read configuration version ----
var macrosVersionContent = fileread("0:/sys/version.txt", 0, 2, ',')
var macrosVersion = var.macrosVersionContent[0] ^ ""
var macrosReleaseDate = var.macrosVersionContent[1] ^ ""


; ---- Create Temperature Log File (fixed name — each run overwrites previous) ----
; Start logging immediately so we capture cooldown, homing, and chamber pre-heat data
var logFile = "0:/sys/logs/pid_bed.csv"
var paramFile = "0:/sys/logs/pid_bed.txt"
var logLine = ""

; Write test settings
echo >{var.logFile} "# PID Tuning — Bed Heater (H2)"
echo >>{var.logFile} "# Target: " ^ var.bedTargetTemp ^ "C"
echo >>{var.logFile} "# Chamber: " ^ (var.useChamber ? "Yes" : "No")
echo >>{var.logFile} "# HEPA: " ^ (var.hepaOn ? "Forced On" : "Thermostatic")
echo >>{var.logFile} "# Old Model: R" ^ heat.heaters[2].model.heatingRate ^ " K" ^ heat.heaters[2].model.coolingRate ^ " D" ^ heat.heaters[2].model.deadTime ^ " E" ^ heat.heaters[2].model.coolingExp
echo >>{var.logFile} "# Mainboard FW: " ^ boards[0].firmwareFileName ^ " v" ^ boards[0].firmwareVersion
echo >>{var.logFile} "# Expansion FW: " ^ boards[1].firmwareFileName ^ " v" ^ boards[1].firmwareVersion
echo >>{var.logFile} "# Config: v" ^ var.macrosVersion ^ " (" ^ var.macrosReleaseDate ^ ")"
echo >>{var.logFile} "#"

var logHeader = "Elapsed(s),Bed(C),ChamberAir(C),ChamberHeater(C),LeftNozzle(C),RightNozzle(C)"
set var.logHeader = var.logHeader ^ ",BedState,ChamberState,hepaFan(RPM),BedPWM,ChamberPWM"
echo >>{var.logFile} var.logHeader

var startTime = state.upTime
var elapsed = 0

; Log initial state
set var.logLine = "0," ^ heat.heaters[2].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
set var.logLine = var.logLine ^ "," ^ fans[7].rpm
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
echo >>{var.logFile} var.logLine
echo "=== PID Tuning Log Started: " ^ var.logFile ^ " ==="

; ---- Save Heater Configuration Parameters ----
var paramLine = ""
echo >{var.paramFile} "=== PID Tuning Configuration Snapshot ==="
echo >>{var.paramFile} "Date: " ^ state.time
echo >>{var.paramFile} "Heater: H2 (Bed Heater)"
echo >>{var.paramFile} "Target: " ^ var.bedTargetTemp ^ "C"
echo >>{var.paramFile} "Chamber: " ^ (var.useChamber ? "Yes" : "No")
echo >>{var.paramFile} "HEPA: " ^ (var.hepaOn ? "Forced On" : "Thermostatic")
echo >>{var.paramFile} ""

; H0 - Left Nozzle
echo >>{var.paramFile} "--- H0 (Left Nozzle) ---"
set var.paramLine = "M307 H0 R" ^ heat.heaters[0].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[0].model.coolingRate ^ ":" ^ heat.heaters[0].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[0].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[0].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[0].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[0].monitors > 0
  var h0m = 0
  while var.h0m < #heat.heaters[0].monitors
    if heat.heaters[0].monitors[var.h0m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h0m ^ ": condition=" ^ heat.heaters[0].monitors[var.h0m].condition ^ " limit=" ^ heat.heaters[0].monitors[var.h0m].limit ^ " action=" ^ heat.heaters[0].monitors[var.h0m].action
    set var.h0m = var.h0m + 1
echo >>{var.paramFile} ""

; H1 - Right Nozzle
echo >>{var.paramFile} "--- H1 (Right Nozzle) ---"
set var.paramLine = "M307 H1 R" ^ heat.heaters[1].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[1].model.coolingRate ^ ":" ^ heat.heaters[1].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[1].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[1].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[1].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[1].monitors > 0
  var h1m = 0
  while var.h1m < #heat.heaters[1].monitors
    if heat.heaters[1].monitors[var.h1m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h1m ^ ": condition=" ^ heat.heaters[1].monitors[var.h1m].condition ^ " limit=" ^ heat.heaters[1].monitors[var.h1m].limit ^ " action=" ^ heat.heaters[1].monitors[var.h1m].action
    set var.h1m = var.h1m + 1
echo >>{var.paramFile} ""

; H2 - Bed
echo >>{var.paramFile} "--- H2 (Bed Heater) ---"
set var.paramLine = "M307 H2 R" ^ heat.heaters[2].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[2].model.coolingRate ^ ":" ^ heat.heaters[2].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[2].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[2].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[2].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[2].monitors > 0
  var h2m = 0
  while var.h2m < #heat.heaters[2].monitors
    if heat.heaters[2].monitors[var.h2m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h2m ^ ": condition=" ^ heat.heaters[2].monitors[var.h2m].condition ^ " limit=" ^ heat.heaters[2].monitors[var.h2m].limit ^ " action=" ^ heat.heaters[2].monitors[var.h2m].action
    set var.h2m = var.h2m + 1
echo >>{var.paramFile} ""

; H3 - Chamber
echo >>{var.paramFile} "--- H3 (Chamber Heater) ---"
set var.paramLine = "M307 H3 R" ^ heat.heaters[3].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[3].model.coolingRate ^ ":" ^ heat.heaters[3].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[3].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[3].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[3].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[3].monitors > 0
  var h3m = 0
  while var.h3m < #heat.heaters[3].monitors
    if heat.heaters[3].monitors[var.h3m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h3m ^ ": condition=" ^ heat.heaters[3].monitors[var.h3m].condition ^ " limit=" ^ heat.heaters[3].monitors[var.h3m].limit ^ " action=" ^ heat.heaters[3].monitors[var.h3m].action
    set var.h3m = var.h3m + 1

; Heater PWM and Heating Rates
echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Heater PWM (at test start) ==="
echo >>{var.paramFile} "H0 (Left Nozzle)  avgPwm: " ^ heat.heaters[0].avgPwm
echo >>{var.paramFile} "H1 (Right Nozzle) avgPwm: " ^ heat.heaters[1].avgPwm
echo >>{var.paramFile} "H2 (Bed Heater)   avgPwm: " ^ heat.heaters[2].avgPwm
echo >>{var.paramFile} "H3 (Chamber)      avgPwm: " ^ heat.heaters[3].avgPwm

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Heating Rates (from model) ==="
echo >>{var.paramFile} "H0 heatingRate: " ^ heat.heaters[0].model.heatingRate
echo >>{var.paramFile} "H1 heatingRate: " ^ heat.heaters[1].model.heatingRate
echo >>{var.paramFile} "H2 heatingRate: " ^ heat.heaters[2].model.heatingRate
echo >>{var.paramFile} "H3 heatingRate: " ^ heat.heaters[3].model.heatingRate

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Board Voltages (at test start) ==="
echo >>{var.paramFile} "Board 0 Vin: " ^ boards[0].vIn.current ^ "V (min: " ^ boards[0].vIn.min ^ " max: " ^ boards[0].vIn.max ^ ")"
echo >>{var.paramFile} "Board 1 Vin: " ^ boards[1].vIn.current ^ "V (min: " ^ boards[1].vIn.min ^ " max: " ^ boards[1].vIn.max ^ ")"

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== MCU Temperatures (at test start) ==="
echo >>{var.paramFile} "Board 0 MCU: " ^ boards[0].mcuTemp.current ^ "C (min: " ^ boards[0].mcuTemp.min ^ " max: " ^ boards[0].mcuTemp.max ^ ")"
echo >>{var.paramFile} "Board 1 MCU: " ^ boards[1].mcuTemp.current ^ "C (min: " ^ boards[1].mcuTemp.min ^ " max: " ^ boards[1].mcuTemp.max ^ ")"

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Firmware ==="
echo >>{var.paramFile} "Board 0: " ^ boards[0].firmwareFileName ^ " v" ^ boards[0].firmwareVersion
echo >>{var.paramFile} "Board 1: " ^ boards[1].firmwareFileName ^ " v" ^ boards[1].firmwareVersion
echo >>{var.paramFile} "Config:  v" ^ var.macrosVersion ^ " (" ^ var.macrosReleaseDate ^ ")"

echo "Heater parameters saved to: " ^ var.paramFile

; ---- Save old model parameters to detect if tuning actually updated them ----
var oldHeatingRate = heat.heaters[2].model.heatingRate
var oldCoolingRate = heat.heaters[2].model.coolingRate
var oldDeadTime = heat.heaters[2].model.deadTime
var oldCoolingExp = heat.heaters[2].model.coolingExp

; Only wait for bed to cool down if target is 140°C or below, or if skipping chamber
if var.bedTargetTemp <= 140 || !var.useChamber
    if heat.heaters[2].current > 50
        var coolMsg = "Bed is at " ^ heat.heaters[2].current ^ "°C. Needs to cool below 50°C."
        set var.coolMsg = var.coolMsg ^ "<br><br>Wait for cool down, or skip to start now."
        M291 R"Bed Cool Down" P{var.coolMsg} S4 K{"Wait for cool down","Skip - start now"}
        if input == 0
            M291 R"Cooling Down..." P{"Waiting for bed to cool below 50°C.<br>Current: " ^ heat.heaters[2].current ^ "°C"} S1 T10
            while heat.heaters[2].current > 50
                G4 S1
                set var.elapsed = state.upTime - var.startTime
                set var.logLine = var.elapsed ^ "," ^ heat.heaters[2].current
                set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
                set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
                set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
                set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
                set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
                set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
                set var.logLine = var.logLine ^ "," ^ fans[7].rpm
                set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
                set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
                echo >>{var.logFile} var.logLine
            echo "Bed cooled to " ^ heat.heaters[2].current ^ "°C"
        else
            echo "User skipped bed cool down (bed at " ^ heat.heaters[2].current ^ "°C)"


; ---- Apply HEPA filter setting ----
if var.hepaOn
    M106 P7 H-1 S{global.hepaFan}                                                         ; Force HEPA on at saved speed
    echo "HEPA filter forced ON at speed " ^ global.hepaFan

; ---- LED: Blue for heating phase ----
M98 P"0:/sys/led/dimmwhite.g"
M98 P"0:/sys/led/start_cold.g"

; ---- Home all axes if not already homed (always, not just for chamber mode) ----
if !move.axes[0].homed || !move.axes[1].homed || !move.axes[2].homed || !move.axes[3].homed
    M291 R"Homing" P"Homing all axes before positioning..." S1 T5
    M98 P"homeall.g" S1 N1                                                               ; N1 = skip LED reset

; If chamber heating selected, lower bed and wait for chamber to reach 60°C
if var.useChamber
    G1 Z350 F600
    M400
    M291 R"Chamber Pre-Heat" P"Waiting for chamber to reach 60°C...<br>Ensure front door and lid are closed." S1 T10
    while heat.heaters[3].current < 60
        G4 S1
        set var.elapsed = state.upTime - var.startTime
        set var.logLine = var.elapsed ^ "," ^ heat.heaters[2].current
        set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
        set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
        set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
        set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
        set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
        set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
        set var.logLine = var.logLine ^ "," ^ fans[7].rpm
        set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
        set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
        echo >>{var.logFile} var.logLine
    echo "Chamber reached " ^ heat.heaters[3].current ^ "°C - proceeding with PID tuning"

; Validate chamber air temperature before tuning
var ambient = heat.heaters[3].current
echo >>{var.logFile} "# Ambient at tuning start: " ^ var.ambient ^ "C"
if var.useChamber
    if var.ambient < 10
        M98 P"0:/sys/led/fault.g"
        M291 R"Chamber Temperature Too Low" P{"Chamber air temperature "^var.ambient^"°C is below 10°C."} S2
        M141 S0
        if var.hepaOn
            M106 P7 H3 T50:90 L0.25 X{global.hepaFan}
        abort "Error: Chamber air temperature is not within range to start."

; ---- LED: Yellow for tuning phase ----
M98 P"0:/sys/led/statusoff.g"
M98 P"0:/sys/led/pause.g"

; ---- Step 1: Start M303 and check if the command was accepted ----
M303 H2 S{var.bedTargetTemp} A{var.ambient} Q1
if result != 0
    M98 P"0:/sys/led/fault.g"
    echo >>{var.logFile} "RESULT: M303 command rejected (result=" ^ result ^ ")"
    var rejectMsg = "PID tuning could not start.<br><br>M303 was rejected by the firmware."
    set var.rejectMsg = var.rejectMsg ^ "<br>Check that bed heater (H2) is configured and not in use."
    M291 R"PID Tune Failed" P{var.rejectMsg} S2
    M98 P"0:/sys/led/resetstatus.g"
    M141 S0
    if var.hepaOn
        M106 P7 H3 T50:90 L0.25 X{global.hepaFan}
    abort "Error: M303 command was rejected — PID tuning could not start."

; ---- Step 2: Verify that tuning actually entered the "tuning" state ----
G4 S2                                                                                     ; Brief pause to let the heater state update
var tuningStarted = (heat.heaters[2].state == "tuning")

if !var.tuningStarted
    M98 P"0:/sys/led/fault.g"
    echo >>{var.logFile} "RESULT: Heater never entered tuning state (state=" ^ heat.heaters[2].state ^ ")"
    var stateMsg = "PID tuning was accepted but the heater never entered the <b>tuning</b> state.<br><br>"
    set var.stateMsg = var.stateMsg ^ "Current heater state: <b>" ^ heat.heaters[2].state ^ "</b><br><br>"
    set var.stateMsg = var.stateMsg ^ "This may indicate a hardware issue or heater fault."
    M291 R"PID Tune Failed" P{var.stateMsg} S2
    if heat.heaters[2].state == "fault"
        M562                                                                              ; Reset heater faults
    M98 P"0:/sys/led/resetstatus.g"
    M141 S0
    if var.hepaOn
        M106 P7 H3 T50:90 L0.25 X{global.hepaFan}
    abort "Error: Heater never entered tuning state."

; ---- Step 3: Monitor tuning progress ----
while heat.heaters[2].state == "tuning"
    G4 S1
    set var.elapsed = state.upTime - var.startTime
    set var.logLine = var.elapsed ^ "," ^ heat.heaters[2].current
    set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
    set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
    set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
    set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
    set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
    set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
    set var.logLine = var.logLine ^ "," ^ fans[7].rpm
    set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
    set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
    echo >>{var.logFile} var.logLine

; ---- Step 4: Post-Tuning Validation ----
; When tuning ends, heater goes to "fault" on failure or "off" on success.

; 4a. Check for heater fault — this is the definitive failure indicator
if heat.heaters[2].state == "fault"
    M98 P"0:/sys/led/fault.g"
    ; Determine why it failed: did the temperature reach the target?
    if heat.heaters[2].current < (var.bedTargetTemp - 20)
        ; Temperature was NOT reached
        echo >>{var.logFile} "RESULT: PID tuning FAILED - target temperature was not reached"
        var tempMsg = "Auto tune cancelled — <b>target temperature was not reached</b>.<br><br>"
        set var.tempMsg = var.tempMsg ^ "Target: " ^ var.bedTargetTemp ^ "°C | Current: " ^ heat.heaters[2].current ^ "°C<br><br>"
        set var.tempMsg = var.tempMsg ^ "<b>Possible causes:</b><br>"
        set var.tempMsg = var.tempMsg ^ "• Heater is underpowered for the target temperature<br>"
        set var.tempMsg = var.tempMsg ^ "• Poor bed insulation or open chamber<br>"
        set var.tempMsg = var.tempMsg ^ "• Faulty heater wiring or thermistor<br>"
        set var.tempMsg = var.tempMsg ^ "• Ambient temperature too low"
        M291 R"PID Tune Failed" P{var.tempMsg} S2
    else
        ; Temperature WAS reached, but tuning still failed
        echo >>{var.logFile} "RESULT: PID tuning FAILED - heater fault (temperature was reached)"
        var hwMsg = "PID tuning <b>failed</b> — the bed heater entered a fault state.<br><br>"
        set var.hwMsg = var.hwMsg ^ "The target temperature (" ^ var.bedTargetTemp ^ "°C) was reached, but tuning could not complete.<br><br>"
        set var.hwMsg = var.hwMsg ^ "<b>Possible causes:</b><br>"
        set var.hwMsg = var.hwMsg ^ "• Heater temperature oscillation too large<br>"
        set var.hwMsg = var.hwMsg ^ "• Thermistor reading unstable<br>"
        set var.hwMsg = var.hwMsg ^ "• Electrical interference or loose wiring"
        M291 R"PID Tune Failed" P{var.hwMsg} S2
    M562                                                                                  ; Reset heater faults
    M98 P"0:/sys/led/resetstatus.g"
    M141 S0
    if var.hepaOn
        M106 P7 H3 T50:90 L0.25 X{global.hepaFan}
    abort "PID tuning failed - heater fault after tuning"

; 4b. Heater is not in fault — verify model parameters actually changed (extra safety)
if heat.heaters[2].model.heatingRate == var.oldHeatingRate && heat.heaters[2].model.deadTime == var.oldDeadTime
    ; Model params didn't change — tuning may not have completed properly
    M98 P"0:/sys/led/fault.g"
    echo >>{var.logFile} "RESULT: PID tuning FAILED - model parameters unchanged"
    var noChangeMsg = "PID tuning completed without errors, but the heater model parameters <b>did not change</b>.<br><br>"
    set var.noChangeMsg = var.noChangeMsg ^ "This may indicate that tuning did not produce valid results.<br><br>"
    set var.noChangeMsg = var.noChangeMsg ^ "Consider retrying with different settings."
    M291 R"PID Tune Warning" P{var.noChangeMsg} S2
    M98 P"0:/sys/led/resetstatus.g"
    M141 S0
    if var.hepaOn
        M106 P7 H3 T50:90 L0.25 X{global.hepaFan}
    abort "PID tuning failed - model parameters did not change"

; ---- Tuning succeeded — save results first, then notify user ----

; Log final entry
var totalTime = state.upTime - var.startTime
set var.logLine = var.totalTime ^ "," ^ heat.heaters[2].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
set var.logLine = var.logLine ^ "," ^ fans[7].rpm
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
echo >>{var.logFile} var.logLine
echo >>{var.logFile} "RESULT: PID tuning completed in " ^ var.totalTime ^ "s"
echo "=== PID Tuning Log Complete: " ^ var.totalTime ^ "s ==="

; Read new model parameters
var rr = heat.heaters[2].model.heatingRate
var kk1 = heat.heaters[2].model.coolingRate
var kk2 = heat.heaters[2].model.fanCoolingRate
var dd = heat.heaters[2].model.deadTime
var ee = heat.heaters[2].model.coolingExp
var vv = heat.heaters[2].model.standardVoltage

; Save PID values to file
echo >"0:/sys/user/actions/PIDBedHead.g" "M307 H2 R"^{var.rr}^" K"^{var.kk1}^":"^{var.kk2}^" D"^{var.dd}^" E"^{var.ee}^" S1.00 B0"

M141 S0
echo "Chamber heater turned off after PID tuning"

; ---- Restore HEPA filter to default thermostatic mode ----
M106 P7 H3 T50:90 L0.25 X{global.hepaFan}
echo "HEPA filter restored to default thermostatic mode"

; ---- LED: Green for success ----
M98 P"0:/sys/led/end.g"

; Build old vs new comparison message
var okMsg = "<b>Bed PID tuning complete!</b><br><br>"
set var.okMsg = var.okMsg ^ "<b>Old values:</b><br>"
set var.okMsg = var.okMsg ^ "R" ^ var.oldHeatingRate ^ " K" ^ var.oldCoolingRate
set var.okMsg = var.okMsg ^ " D" ^ var.oldDeadTime ^ " E" ^ var.oldCoolingExp ^ "<br><br>"
set var.okMsg = var.okMsg ^ "<b>New values:</b><br>"
set var.okMsg = var.okMsg ^ "R" ^ var.rr ^ " K" ^ var.kk1
set var.okMsg = var.okMsg ^ " D" ^ var.dd ^ " E" ^ var.ee
M291 R"PID Tune Complete" P{var.okMsg} S2

; ---- LED: Return to white after user acknowledges ----
M98 P"0:/sys/led/resetstatus.g"
